package com.ruoyi.learn.java.design.pattern.behavior;

/**
 * 命令模式 (Command Pattern) 示例：实现智能家居遥控器，支持撤销操作。
 */
public class CommandPatternDemo {

    //region ========== 1. 接收者 (Receiver) ==========

    /**
     * 接收者：电灯
     * 知道如何执行“开”和“关”灯的操作。
     */
    public static class Light {
        private boolean isOn = false;
        private String location;

        public Light(String location) {
            this.location = location;
        }

        public void on() {
            isOn = true;
            System.out.println("  > 【电灯】" + location + " 的灯打开了。");
        }

        public void off() {
            isOn = false;
            System.out.println("  > 【电灯】" + location + " 的灯关闭了。");
        }

        public boolean isOn() {
            return isOn;
        }
    }

    /**
     * 接收者：音响
     * 知道如何执行“播放”和“停止”音乐的操作。
     */
    public static class Stereo {
        private boolean isPlaying = false;
        private String location;

        public Stereo(String location) {
            this.location = location;
        }

        public void play() {
            isPlaying = true;
            System.out.println("  > 【音响】" + location + " 的音响开始播放音乐。");
        }

        public void stop() {
            isPlaying = false;
            System.out.println("  > 【音响】" + location + " 的音响停止了音乐。");
        }

        public boolean isPlaying() {
            return isPlaying;
        }
    }

    //endregion

    //region ========== 2. 命令 (Command) ==========

    /**
     * 命令接口
     * 定义了执行请求和撤销请求的方法。
     */
    public interface Command {
        /**
         * 执行请求
         */
        void execute();

        /**
         * 撤销请求
         */
        void undo();
    }

    //endregion

    //region ========== 3. 具体命令 (ConcreteCommand) ==========

    /**
     * 具体命令：开灯命令
     * 封装了“开灯”这个请求。
     */
    public static class LightOnCommand implements Command {
        private Light light; // 持有对接收者的引用

        public LightOnCommand(Light light) {
            this.light = light;
        }

        @Override
        public void execute() {
            // 执行“开灯”操作
            light.on();
        }

        @Override
        public void undo() {
            // 撤销“开灯”操作，即“关灯”
            light.off();
        }
    }

    /**
     * 具体命令：关灯命令
     * 封装了“关灯”这个请求。
     */
    public static class LightOffCommand implements Command {
        private Light light; // 持有对接收者的引用

        public LightOffCommand(Light light) {
            this.light = light;
        }

        @Override
        public void execute() {
            // 执行“关灯”操作
            light.off();
        }

        @Override
        public void undo() {
            // 撤销“关灯”操作，即“开灯”
            light.on();
        }
    }

    /**
     * 具体命令：播放音乐命令
     * 封装了“播放音乐”这个请求。
     */
    public static class StereoPlayCommand implements Command {
        private Stereo stereo; // 持有对接收者的引用

        public StereoPlayCommand(Stereo stereo) {
            this.stereo = stereo;
        }

        @Override
        public void execute() {
            // 执行“播放音乐”操作
            stereo.play();
        }

        @Override
        public void undo() {
            // 撤销“播放音乐”操作，即“停止音乐”
            stereo.stop();
        }
    }

    /**
     * 具体命令：停止音乐命令
     * 封装了“停止音乐”这个请求。
     */
    public static class StereoStopCommand implements Command {
        private Stereo stereo; // 持有对接收者的引用

        public StereoStopCommand(Stereo stereo) {
            this.stereo = stereo;
        }

        @Override
        public void execute() {
            // 执行“停止音乐”操作
            stereo.stop();
        }

        @Override
        public void undo() {
            // 撤销“停止音乐”操作，即“播放音乐”
            stereo.play();
        }
    }

    //endregion

    //region ========== 4. 调用者 (Invoker) ==========

    /**
     * 调用者：遥控器
     * 持有命令对象的引用，并在用户按下按钮时调用命令的 execute() 方法。
     * 支持撤销操作。
     */
    public static class RemoteControl {
        private Command onCommand;   // 存储“开”命令
        private Command offCommand;  // 存储“关”命令
        private Command undoCommand; // 存储上一次执行的命令，用于撤销

        public RemoteControl() {
            // 初始化时，命令为空
            this.onCommand = null;
            this.offCommand = null;
            this.undoCommand = new NoCommand(); // 初始撤销命令为空操作
        }

        /**
         * 设置“开”按钮对应的命令
         * @param command 开命令
         */
        public void setOnCommand(Command command) {
            this.onCommand = command;
        }

        /**
         * 设置“关”按钮对应的命令
         * @param command 关命令
         */
        public void setOffCommand(Command command) {
            this.offCommand = command;
        }

        /**
         * 模拟按下“开”按钮
         */
        public void pressOnButton() {
            if (onCommand != null) {
                System.out.println("\n=== 按下【开】按钮 ===");
                onCommand.execute(); // 执行开命令
                undoCommand = onCommand; // 记录本次执行的命令，用于撤销
            } else {
                System.out.println("  > 【遥控器】'开'按钮未设置命令。");
            }
        }

        /**
         * 模拟按下“关”按钮
         */
        public void pressOffButton() {
            if (offCommand != null) {
                System.out.println("\n=== 按下【关】按钮 ===");
                offCommand.execute(); // 执行关命令
                undoCommand = offCommand; // 记录本次执行的命令，用于撤销
            } else {
                System.out.println("  > 【遥控器】'关'按钮未设置命令。");
            }
        }

        /**
         * 模拟按下“撤销”按钮
         */
        public void pressUndoButton() {
            System.out.println("\n=== 按下【撤销】按钮 ===");
            undoCommand.undo(); // 执行撤销命令
            // 执行 undo 后，通常将 undoCommand 置为空或设置为相反操作，
            // 这里简单处理，再次撤销会回到上上个状态（可改进）
            // 例如：执行 undo 后，可以将 undoCommand 设置为执行 undo 时所做的操作。
            // 为了演示，我们不改变 undoCommand，连续按撤销会有特定行为。
        }
    }

    //endregion

    //region ========== 5. 辅助类 ==========

    /**
     * 空命令 (NoCommand)
     * 一个实现 Command 接口的空操作类。
     * 用于避免空指针异常，提供默认行为。
     */
    public static class NoCommand implements Command {
        @Override
        public void execute() {
            // 什么也不做
            System.out.println("  > 【空命令】无操作。");
        }

        @Override
        public void undo() {
            // 什么也不做
            System.out.println("  > 【空命令】无操作可撤销。");
        }
    }

    //endregion

    //region ========== 6. 主函数 (Main) - 演示命令模式 ==========

    public static void main(String[] args) {
        System.out.println("=== 命令模式演示 ===\n");

        // 1. 创建接收者 (Receiver)
        System.out.println("1. 创建智能家居设备（接收者）...");
        Light livingRoomLight = new Light("客厅");
        Light kitchenLight = new Light("厨房");
        Stereo livingRoomStereo = new Stereo("客厅");

        // 2. 创建具体命令 (ConcreteCommand)，并绑定接收者
        System.out.println("2. 创建具体命令对象...");
        Command livingRoomLightOn = new LightOnCommand(livingRoomLight);
        Command livingRoomLightOff = new LightOffCommand(livingRoomLight);
        Command kitchenLightOn = new LightOnCommand(kitchenLight);
        Command kitchenLightOff = new LightOffCommand(kitchenLight);
        Command livingRoomStereoPlay = new StereoPlayCommand(livingRoomStereo);
        Command livingRoomStereoStop = new StereoStopCommand(livingRoomStereo);

        // 3. 创建调用者 (Invoker) 并设置命令
        System.out.println("3. 创建遥控器（调用者）并设置按钮...");
        RemoteControl remote = new RemoteControl();
        // 为遥控器的“开”按钮设置“客厅开灯”命令
        remote.setOnCommand(livingRoomLightOn);
        // 为遥控器的“关”按钮设置“客厅关灯”命令
        remote.setOffCommand(livingRoomLightOff);

        // 4. 演示基本操作和撤销
        System.out.println("4. 演示【客厅灯】的基本操作和撤销...\n");

        // --- 操作 1: 开灯 ---
        remote.pressOnButton(); // 执行: 开灯

        // --- 操作 2: 撤销 ---
        remote.pressUndoButton(); // 执行: 撤销 -> 关灯 (undo of LightOnCommand is off)

        // --- 操作 3: 关灯 (但灯现在是关的) ---
        remote.pressOffButton(); // 执行: 关灯 (虽然灯已关，但命令仍会执行)

        // --- 操作 4: 撤销 ---
        remote.pressUndoButton(); // 执行: 撤销 -> 开灯 (undo of LightOffCommand is on)

        // 5. 演示动态切换命令
        System.out.println("5. 演示【动态切换遥控器命令】...\n");

        // 现在，遥控器的“开”和“关”按钮被重新设置为控制厨房灯
        System.out.println("  > 【遥控器】现在控制【厨房灯】！");
        remote.setOnCommand(kitchenLightOn);
        remote.setOffCommand(kitchenLightOff);

        remote.pressOnButton(); // 执行: 厨房开灯
        remote.pressUndoButton(); // 执行: 撤销 -> 厨房关灯

        // 6. 演示控制不同设备
        System.out.println("6. 演示【控制音响】...\n");

        // 再次切换遥控器，控制客厅音响
        System.out.println("  > 【遥控器】现在控制【客厅音响】！");
        remote.setOnCommand(livingRoomStereoPlay);
        remote.setOffCommand(livingRoomStereoStop);

        remote.pressOnButton(); // 执行: 播放音乐
        remote.pressUndoButton(); // 执行: 撤销 -> 停止音乐

        // 7. 命令模式的优势
        System.out.println("\n--- 命令模式的优势 ---");
        System.out.println("• **解耦**：遥控器 (Invoker) 与具体的设备 (Receiver) 完全解耦。");
        System.out.println("  遥控器只知道 Command 接口，不知道是灯还是音响。");
        System.out.println("• **可扩展性**：可以轻松添加新的命令（如调光、切换音源）");
        System.out.println("  和新的设备（如风扇、电视），只需实现 Command 接口，");
        System.out.println("  无需修改遥控器代码。符合开闭原则。");
        System.out.println("• **支持撤销/重做**：每个命令对象可以实现 undo() 方法，");
        System.out.println("  实现撤销功能变得非常简单和统一。");
        System.out.println("• **支持宏命令**：可以创建一个 MacroCommand，");
        System.out.println("  包含多个 Command，实现一键执行多个操作。");
        System.out.println("• **支持队列和日志**：命令对象可以被放入队列异步执行，");
        System.out.println("  或被序列化记录，用于日志恢复。");

        // 8. 注意事项
        System.out.println("\n--- 注意事项 ---");
        System.out.println("• **类爆炸**：每个操作都需要一个具体的命令类，");
        System.out.println("  可能导致类的数量急剧增加。可以考虑使用匿名内部类、");
        System.out.println("  Lambda 表达式（Java 8+）或组合模式来缓解。");
        System.out.println("• **内存开销**：每个命令对象都持有一个或多个接收者的引用，");
        System.out.println("  如果命令对象长期存在（如在队列中），会占用内存。");
        System.out.println("• **状态管理**：对于复杂的撤销操作（如文本编辑器），");
        System.out.println("  命令对象可能需要存储大量的状态信息。");
        System.out.println("• **事务性**：如果一组命令需要作为一个事务执行（全成功或全失败），");
        System.out.println("  需要额外的机制（如事务日志）来保证。");
        System.out.println("• **命令生命周期**：需要考虑命令对象的创建、执行、");
        System.out.println("  撤销和销毁的生命周期管理。");

        // 9. 使用 Lambda 表达式简化 (Java 8+)
        System.out.println("\n--- 使用 Lambda 表达式简化 (Java 8+) ---");
        System.out.println("在 Java 8+ 中，如果 Command 接口是函数式接口 (只有一个抽象方法)，");
        System.out.println("可以使用 Lambda 表达式来创建命令，避免创建大量具体类。");

        // 假设我们有一个更通用的命令接口（或使用 Runnable）
        // 这里用 Runnable 演示（execute 对应 run）
        java.util.function.Consumer<Runnable> simpleRemote = (command) -> {
            System.out.println("  > 【简易遥控器】执行命令...");
            command.run();
        };

        simpleRemote.accept(() -> livingRoomLight.on()); // Lambda 作为“开灯”命令
        simpleRemote.accept(() -> livingRoomLight.off()); // Lambda 作为“关灯”命令

        // 对于需要 undo 的复杂场景，Lambda 可能不够用，仍需具体类。
    }

    //endregion
}
